#define __SHORT_CIRCUIT_ET__

// Need to short circuit macros for min/max/floor/ceil to enable ET
#include "polyTask.hpp"
#include "Wrapper.hpp"
#include "constants.h"

#include "./simple_expression_template/VAR.hpp"
#include "./simple_expression_template/TERM.hpp"
#include "./simple_expression_template/EXPRESSION.hpp"
#include "./simple_expression_template/ADD.hpp"
#include "./simple_expression_template/SUB.hpp"
#include "./simple_expression_template/MIN.hpp"
#include "./simple_expression_template/MAX.hpp"
#include "./simple_expression_template/CEIL.hpp"
#include "./simple_expression_template/FLOOR.hpp"
#include "./simple_expression_template/GE.hpp"
#include "./simple_expression_template/LE.hpp"
#include "./simple_expression_template/FOR.hpp"

#undef __SHORT_CIRCUIT_ET__

#include "TupleHelper.hpp"
template<typename CONTEXT, typename ITEM_COLLECTION>
class CNCGetPutWrapper {
  CONTEXT &ctx;
  ITEM_COLLECTION &ic;

public:
  CNCGetPutWrapper(CONTEXT &c, ITEM_COLLECTION &i) : ctx(c), ic(i) {}

  template<int TASK_ID, int TASKTAGDIM, int INTERTASKDIM>
  void get(TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> &t, void * &wA) {
#ifdef MEASURE_OVERHEADS
    Timer oh(ctx.overheadCNCGetPut);
#endif // MEASURE_OVERHEADS
    ic.get(t, wA);
  }
  
  template<int TASK_ID, int TASKTAGDIM, int INTERTASKDIM>
  void put(TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> &t, void * &wA) {
#ifdef MEASURE_OVERHEADS
    Timer oh(ctx.overheadCNCGetPut);
#endif // MEASURE_OVERHEADS
    ic.put(t, wA);
  }
};


template<typename DC, typename CONTEXT>
class CNCDepWrapper {
  CONTEXT &ctx;
  DC &dc;

public:
  CNCDepWrapper(CONTEXT &c, DC &d) : ctx(c), dc(d) {}
  
  template<typename ITEM_COLLECTION, int TASK_ID, int TASKTAGDIM, int INTERTASKDIM>
  void depends(ITEM_COLLECTION &ic, TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> &t) {
#ifdef MEASURE_OVERHEADS
    Timer oh(ctx.overheadCNCDep);
#endif // MEASURE_OVERHEADS
    dc.depends(ic, t);
  }
};



// Depth N-1 does   1 updates to std::get<N>
// Depth N-2 does   2 updates to std::get<N-1>
// ...
// Depth N-k does N-k updates to std::get<N-k>
// ...
// Depth   1 does   N updates to std::get<1>
// Depth   0 does   0 updates to std::get<1>
// -> must start with N = INTERTASKDIM-1 and K=1
template<int TASKTAGDIM, int INTERTASKDIM, int N, int K> struct computePredsHelper2 {
  template<typename...CONDSE, typename...DEPDISTS, typename...LBSE, typename...UBSE, typename... INTS>
  static void compute(tuple<CONDSE...> &condsE, tuple<DEPDISTS...> const & dists, 
                      tuple<LBSE...> const & lbsE, tuple<UBSE...> const & ubsE, 
                      tuple<INTS...> & tagAsTupleE)
  {
#if (DEBUG_ET_DEPS && VERBOSE >= 100)
    std::cerr << "At loop " << N << " cond is " << std::get<N>(condsE) << "\n";
#endif

    if (std::get<N>(dists) != 0) {
      auto tmp = std::get<N>(tagAsTupleE);
      std::get<N>(tagAsTupleE) = tmp - std::get<N>(dists);  // DEP2
      std::get<N>(condsE) |= 
        !(std::get<INTERTASKDIM-K+1>(lbsE).Val(tagAsTupleE)) ||
        !(std::get<INTERTASKDIM-K+1>(ubsE).Val(tagAsTupleE));
      assert(std::get<N>(tagAsTupleE) == tmp - std::get<N>(dists));  // DEP2
      std::get<N>(tagAsTupleE) = tmp;
      assert(std::get<N>(tagAsTupleE) == tmp);
    }

#if (DEBUG_ET_DEPS && VERBOSE >= 100)
    std::cerr << "\t\tEvaluating expression for loop " << N << " at loop iteration: ";
    std::cerr << std::get<1>(tagAsTupleE) << " ";
    std::cerr << std::get<2>(tagAsTupleE) << " ";
    std::cerr << std::get<3>(tagAsTupleE) << " ";
    std::cerr << std::get<4>(tagAsTupleE) << " ";
    std::cerr << std::get<5>(tagAsTupleE) << " ";
    std::cerr << " gives " << std::get<N>(condsE) << "\n";
#endif

    computePredsHelper2<TASKTAGDIM, INTERTASKDIM, N, K-1>::compute
      (condsE, dists, lbsE, ubsE, tagAsTupleE);
  }
};


template<int TASKTAGDIM, int INTERTASKDIM, int N> 
struct computePredsHelper2<TASKTAGDIM, INTERTASKDIM, N, 0> {
  template<typename...CONDSE, typename...DEPDISTS, typename...LBSE, typename...UBSE, typename... INTS>
  static void compute(tuple<CONDSE...> &condsE, tuple<DEPDISTS...> const &dists, tuple<LBSE...> const & lbsE, 
                            tuple<UBSE...> const & ubsE, tuple<INTS...> & tagAsTupleE) {}
};


// Depth N-1 does   1 updates to std::get<N>
// Depth N-2 does   2 updates to std::get<N-1>
// ...
// Depth N-k does N-k updates to std::get<N-k>
// ...
// Depth   1 does   N updates to std::get<1>
// Depth   0 does   0 updates to std::get<1>
// -> must start with N = INTERTASKDIM-1 and K=1
template<int TASKTAGDIM, int INTERTASKDIM, int K, int N> struct computePredsHelper {
  template<typename...CONDSE, typename...DEPDISTS, typename...LBSE, typename...UBSE, typename... INTS>
  static void compute(tuple<CONDSE...> &condsE, tuple<DEPDISTS...> const &dists,
                      tuple<LBSE...> const & lbsE, tuple<UBSE...> const &ubsE, 
                      tuple<INTS...> & tagAsTupleE)
  {
    computePredsHelper2<TASKTAGDIM, INTERTASKDIM, N, K>::compute
      (condsE, dists, lbsE, ubsE, tagAsTupleE);

    computePredsHelper<TASKTAGDIM, INTERTASKDIM, K+1, N-1>::compute
      (condsE, dists, lbsE, ubsE, tagAsTupleE);
  }
};


template<int TASKTAGDIM, int INTERTASKDIM, int K> 
struct computePredsHelper<TASKTAGDIM, INTERTASKDIM, K, 0> {
  template<typename...CONDSE, typename...DEPDISTS, typename...LBSE, typename...UBSE, typename... INTS>
  static void compute(tuple<CONDSE...> &condsE, tuple<DEPDISTS...> const &dists, tuple<LBSE...> const & lbsE, 
                      tuple<UBSE...> const & ubsE, tuple<INTS...> & tagAsTupleE) {}
};


template<int TASK_ID, int TASKTAGDIM, int INTERTASKDIM, int N>
struct CNCDeps {
  template<typename CONTEXT, class ITEM_COLLECTION, class DC, typename...CONDSE, typename...DEPDISTS>
  static void genDeps(CONTEXT &ctx, ITEM_COLLECTION & ic, DC &dc,
                      tuple<CONDSE...> &condsE, tuple<DEPDISTS...> const &dists, 
                      TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> &tt) {
    if (std::get<N>(dists) != 0) {
      if (!std::get<N>(condsE)) {
        tt.m_arr[N-1] -= std::get<N>(dists); // DEP2
        TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> t(tt);

#ifdef MEASURE_OVERHEADS
        CNCDepWrapper<DC, CONTEXT> cncw(ctx, dc);
        cncw.depends(ic, t);
#else
        dc.depends(ic, t);
#endif
        tt.m_arr[N-1] += std::get<N>(dists); // DEP2
      }
    }
    CNCDeps<TASK_ID, TASKTAGDIM, INTERTASKDIM, N-1>::genDeps(ctx, ic, dc, condsE, dists, tt);
  }
  
  template<typename CONTEXT, class ITEM_COLLECTION, typename...CONDSE, typename...DEPDISTS>
  static void genGets(CONTEXT &ctx, ITEM_COLLECTION & ic, tuple<CONDSE...> &condsE, 
                      tuple<DEPDISTS...> const &dists, 
                      TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> &tt,
                      void *& wA) {
    if (!std::get<N>(condsE)) {
      std::get<N-1>(tt.m_arr) -= std::get<N>(dists); // DEP2
#if (DEBUG_ET_DEPS && VERBOSE >= 5)
      std::cerr << "Get " << N-1 << " tag: " << tt << "\n";
#endif
      
#ifdef MEASURE_OVERHEADS
      CNCGetPutWrapper<CONTEXT, ITEM_COLLECTION> cncw(ctx, ic);
      TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> tt_copy(tt);
      cncw.get(tt_copy, wA);
#else      
      ic.get(TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> (tt), wA);
#endif
      
#if (DEBUG_ET_DEPS && VERBOSE >= 5)
      std::cerr << "Success\n";
#endif
      tt.m_arr[N-1] += std::get<N>(dists);  // DEP2
    }
    CNCDeps<TASK_ID, TASKTAGDIM, INTERTASKDIM, N-1>::genGets(ctx, ic, condsE, dists, tt, wA);
  }

  template<typename CONTEXT, class ITEM_COLLECTION, typename...CONDSE>
  static void genZeroGet(CONTEXT &ctx, ITEM_COLLECTION &ic, tuple<CONDSE...> &condsE, 
                         void *& wA) {
    // If at least one of the conditions is true, this is not the first task
    // and we should get the task "zero" (zero atm, need to rename).
    if (!std::get<N>(condsE)) return;
    CNCDeps<TASK_ID, TASKTAGDIM, INTERTASKDIM, N-1>::genZeroGet(ctx, ic, condsE, wA);
  }

  template<typename CONTEXT, class ITEM_COLLECTION, class DC, typename...CONDSE>
  static void genZeroDep(CONTEXT &ctx, ITEM_COLLECTION &ic, DC &dc, tuple<CONDSE...> &condsE) {
    // If at least one of the conditions is true, this is not the first task
    // and we should get the task "zero" (zero atm, need to rename).
    if (!std::get<N>(condsE)) return;
    CNCDeps<TASK_ID, TASKTAGDIM, INTERTASKDIM, N-1>::genZeroDep(ctx, ic, dc, condsE);
  }
};

template<int TASK_ID, int TASKTAGDIM, int INTERTASKDIM>
struct CNCDeps<TASK_ID, TASKTAGDIM, INTERTASKDIM, 0> {
  template<typename CONTEXT, class ITEM_COLLECTION, class DC, typename...CONDSE, typename...DEPDISTS>
  static void genDeps(CONTEXT &ctx, ITEM_COLLECTION &ic, DC &dc,
                      tuple<CONDSE...> &condsE, tuple<DEPDISTS...> const &dists,
                      TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> &tt) {}

  template<typename CONTEXT, class ITEM_COLLECTION, typename...CONDSE, typename...DEPDISTS>
  static void genGets(CONTEXT &ctx, ITEM_COLLECTION &ic,
                      tuple<CONDSE...> &condsE, tuple<DEPDISTS...> const &dists,
                      TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> &tt,
                      void *& wA) {}

  template<typename CONTEXT, class ITEM_COLLECTION, typename...CONDSE>
  static void genZeroGet(CONTEXT &ctx, ITEM_COLLECTION &ic, tuple<CONDSE...> &condsE, 
                         void *& wA) {
    // Leaf of the recursion, just get   
    TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> t
      (toArray(makeIdenticalTuple<tag_elem_t, TASKTAGDIM>::make(ZERO_TASK_INDEX)));
#if (DEBUG_ET_DEPS && VERBOSE >= 5)
    std::cerr << "Get dummy " << t << "\n";
#endif

#ifdef MEASURE_OVERHEADS
    CNCGetPutWrapper<CONTEXT, ITEM_COLLECTION> cncw(ctx, ic);
    cncw.get(t, wA);
#else
    ic.get(t, wA);
#endif

#if (DEBUG_ET_DEPS && VERBOSE >= 5)
    std::cerr << "Success\n";
#endif
  }

  template<typename CONTEXT, class ITEM_COLLECTION, class DC, typename...CONDSE>
  static void genZeroDep(CONTEXT &ctx, ITEM_COLLECTION &ic, DC &dc, tuple<CONDSE...> &condsE) {
    // Leaf of the recursion, just get   
    TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> t
      (toArray(makeIdenticalTuple<tag_elem_t, TASKTAGDIM>::make(ZERO_TASK_INDEX)));
#ifdef MEASURE_OVERHEADS
    CNCDepWrapper<DC, CONTEXT> cncw(ctx, dc);
    cncw.depends(ic, t);
#else
    dc.depends(ic, t);
#endif
  }
};
